#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/moduleparam.h>
#include <linux/blk_types.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_cmnd.h>
#include <asm/unaligned.h>

#include "build_cmd.h"

MODULE_AUTHOR("Arkis");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("block");

static char HOLDER[] = "SCSI TEST";
static char *g_devpath = NULL;
const fmode_t GET_DEV_MODE = FMODE_READ | FMODE_WRITE;

static struct scsi_device *g_sdev = NULL;
static struct block_device *g_blkdev = NULL;

module_param(g_devpath, charp, S_IRUGO);

static int get_blk_device(void)
{
    struct request_queue *queue = NULL;
    if (g_devpath == NULL) {
        printk(KERN_ERR "please input device path\n");
        return -1;
    }
    g_blkdev = blkdev_get_by_path(g_devpath, GET_DEV_MODE, HOLDER);
	if (IS_ERR(g_blkdev)) {
        printk(KERN_ERR "get device failed [%s]\n", g_devpath);
        g_blkdev = NULL;
		return -1;
	}
    queue = (struct request_queue *)g_blkdev->bd_queue;
    if (queue == NULL) {
        return -1;
    }
    g_sdev = (struct scsi_device *)queue->queuedata;
    if (g_sdev == NULL) {
        return -1;
    }
    printk(KERN_INFO "get device success\n");
    return 0;
}

static void build_scsi_cmd_pre(scsi_cmd_t *cmd)
{
    cmd->timeout = DEFAULT_SCSI_TIMEOUT;
    cmd->retries = DEFAULT_SCSI_RETRIES;
    memset(cmd->scsi_cmd, 0, sizeof(DEFAULT_SCSI_CMD_SIZE));
}

static int build_scsi_cmd_post(scsi_cmd_t *cmd)
{
    if (cmd->data_out_buffer_len == 0) {
        printk("cmd->data_out_buffer_len == 0\n");
        cmd->data_out_buffer = NULL;
        return 0;
    }
    cmd->data_out_buffer = kmalloc(cmd->data_out_buffer_len, GFP_KERNEL);
    if (cmd->data_out_buffer == NULL) {
        printk("cmd->data_out_buffer == NULL\n");
        return -1;
    }
    memset(cmd->data_out_buffer, 0, cmd->data_out_buffer_len);
    return 0;
}

static int default_scsi_cmd_error(scsi_cmd_t *cmd)
{
    printk(KERN_ERR "default_scsi_cmd_error\n");
    if (cmd->data_out_buffer != NULL) {
        kfree(cmd->data_out_buffer);
        cmd->data_out_buffer = NULL;
    }
    if (cmd->exec_args.sshdr == NULL) {
        return -1;
    }
    printk("response_code=%u\n", cmd->exec_args.sshdr->response_code);
    printk("sense_key=%u\n", cmd->exec_args.sshdr->sense_key);
    printk("asc=%u\n", cmd->exec_args.sshdr->asc);
    printk("ascq=%u\n", cmd->exec_args.sshdr->ascq);
    return -1;
}

static int default_scsi_cmd_done(scsi_cmd_t *cmd)
{
    int ret = 0;
    printk(KERN_INFO "default_scsi_cmd_done\n");
    if (cmd->callback != NULL) {
        ret = cmd->callback(cmd);
    }
    if (cmd->data_out_buffer != NULL) {
        kfree(cmd->data_out_buffer);
        cmd->data_out_buffer = NULL;
    }
    return ret;
}

static int send_scsi_cmd(build_scsi_cmd_t build_scsi_cmd)
{
    scsi_cmd_t cmd = {0};
    struct scsi_sense_hdr hdr = {0};

    if (build_scsi_cmd == NULL) {
        printk(KERN_ERR "build_scsi_cmd is null\n");
        return -1;
    }
    if (g_sdev == NULL) {
        printk(KERN_ERR "sdev is null\n");
        return -1;
    }

    memset(&cmd, 0, sizeof(scsi_cmd_t));
    cmd.sdev = g_sdev;
    cmd.exec_args.sshdr = &hdr;
    build_scsi_cmd_pre(&cmd);
    build_scsi_cmd(&cmd);
    if (build_scsi_cmd_post(&cmd) != 0) {
        printk(KERN_ERR "build_scsi_inquiry_cmd failed\n");
        return -1;
    }
    printk(KERN_INFO "build_scsi_cmd success\n");
    cmd.status = scsi_execute_cmd(
            cmd.sdev,
            cmd.scsi_cmd,
            REQ_OP_DRV_IN,
            cmd.data_out_buffer,
            cmd.data_out_buffer_len,
            cmd.timeout, 
            cmd.retries,
            &cmd.exec_args);
    if (cmd.status != 0) {
        return default_scsi_cmd_error(&cmd);
    } else {
        return default_scsi_cmd_done(&cmd);
    }
}

static int __init test_init(void)
{
    int ret = -1;
    ret = get_blk_device();
    if (ret != 0) {
        return 0;
    }
    send_scsi_cmd(build_scsi_inquiry_0x83_cmd);
    return 0;
}

static void __exit test_exit(void)
{
    if (g_blkdev != NULL) {
        blkdev_put(g_blkdev, GET_DEV_MODE);
    }
}

module_init(test_init)
module_exit(test_exit)
